package vg.services.data_base_manager.data.skeleton;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import vg.services.data_base_manager.data.graph.Attribute;

public class GraphTreeElement extends DefaultMutableTreeNode implements Cloneable, Comparable<GraphTreeElement> {
	private static final long serialVersionUID = 1L;
	
	// Defines. Node types
	public static final int DEF_NO_TYPE            = 0x00000001;
	public static final int DEF_VERTEX_TYPE        = 0x00000010;
	public static final int DEF_EDGE_TYPE          = 0x00000100;
	public static final int DEF_GRAPH_TYPE         = 0x00001000;
	public static final int DEF_GRAPH_MODEL_TYPE   = 0x00010000;
	public static final int DEF_TAB_TYPE           = 0x00100000;
	public static final int DEF_ANY_NODE_TYPE      = 0x00111111;
	
	public static final int DEF_NO_ID = -1;
	
	// Data
	private int vertex_id = DEF_NO_ID;
	private int edge_id = DEF_NO_ID;
	private int tab_id = DEF_NO_ID;
	private int graph_id = DEF_NO_ID;
	private int graph_model_id = DEF_NO_ID;
	
	private int type = DEF_NO_TYPE; // type of node (e.g. VERTEX, SUBGRAPH, GRAPH, TAB ...)
	private List<Attribute> attributes = new ArrayList<Attribute>();
	
	// Optimizations
	private String name = ""; // name of node
	private boolean visible = true; // it's may help for high-speed performance
	
//=============================================================================	
//-----------------Constructors------------------------------------------------
	public GraphTreeElement() {}

	public GraphTreeElement(String name) {
		this.name = name;
	}
	
	public GraphTreeElement(GraphTreeElement e) {
		type = e.type;
		name = e.name;
		
		vertex_id = e.vertex_id;
		edge_id = e.edge_id;
		tab_id = e.tab_id;
		graph_id = e.graph_id;
		graph_model_id = e.graph_model_id;
		
		visible = e.visible;
		attributes = new ArrayList<Attribute>(e.attributes);
		for(int i = 0; i < e.getChildCount(); i++) {
			add(((GraphTreeElement)e.getChildAt(i)).clone());
		}
	}
	
//=============================================================================
//------------------------SET METHODS------------------------------------------
	public void vg_setVertex_id(int vertex_id) {
		this.vertex_id = vertex_id;
		this.type |= DEF_VERTEX_TYPE;
	}
	
	public void vg_setEdge_id(int edge_id) {
		this.edge_id = edge_id;
		this.type |= DEF_EDGE_TYPE;
	}
	
	public void vg_setGraphModel_id_tmp(int graph_model_id) {
		this.graph_model_id = graph_id;
		this.type |= DEF_GRAPH_MODEL_TYPE;
	}
	
	public void vg_setGraph_id_tmp(int graph_id) {
		this.graph_id = graph_id;
		this.type |= DEF_GRAPH_TYPE;
	}
	
	public void vg_setTab_id(int tab_id) {
		this.tab_id = tab_id;
		this.type |= DEF_TAB_TYPE;
	}
	
	public void vg_setName(String name) {
		this.name = name;
	}
	
//=============================================================================
//------------------------GET METHODS------------------------------------------
	/**
	 * This method returns a node, which corresponds the path.
	 * @param path - path to node. If path == null, that this method returns null.
	 */
	public GraphTreeElement vg_getNode(final TreePath path) {
		if(path == null) return null;
		GraphTreeElement result = this;
		int i, j;
		for(i = 0 ; i < path.getPathCount() ; i++) {
			Object e = path.getPath()[i];
			if(result != null && e.equals(result)) {
				if(i+1 < path.getPathCount()) {
					boolean check = false;
					e = path.getPath()[i + 1];
					for(j = 0 ; j < result.getChildCount() ; j++) {
						GraphTreeElement sn = (GraphTreeElement)result.getChildAt(j);
						if(sn != null && sn.equals(e)) {
							result = sn;
							check = true;
							break;
						}
					}
					if(check == false) {
						break;
					}
				}
			} else {
				break;
			}
		}
		if(i == path.getPathCount()) {
			return result;			
		}
		return null;
	}
	
	public GraphTreeElement vg_getNode(int node_id, int node_type) {
		if(node_type == DEF_NO_TYPE) return null;
		Queue<GraphTreeElement> queue = new ArrayDeque<GraphTreeElement>();
		queue.add(this);
		while(!queue.isEmpty()) {
			GraphTreeElement curr = queue.poll();
			if((curr.type & node_type & DEF_GRAPH_MODEL_TYPE) > 0 && curr.graph_model_id == node_id)       return curr;
			if((curr.type & node_type & DEF_GRAPH_TYPE) > 0 && curr.graph_id == node_id)       return curr;
			if((curr.type & node_type & DEF_VERTEX_TYPE) > 0 && curr.vertex_id == node_id)     return curr;
			if((curr.type & node_type & DEF_EDGE_TYPE) > 0 && curr.edge_id == node_id)         return curr;
			if((curr.type & node_type & DEF_TAB_TYPE) > 0 && curr.tab_id == node_id)           return curr;
			
			for(int i = 0 ; i < curr.getChildCount() ; i++) {
				GraphTreeElement child = (GraphTreeElement)curr.getChildAt(i);
				queue.add(child);
			}
			
		}
		return null;
	}
	
	public TreePath vg_getPath(int node_id, int node_type) {
		if(node_type == DEF_NO_TYPE) return null;
		List<TreeNode>path = new ArrayList<TreeNode>();
		if(get_path(node_id, node_type, path)) {
			Collections.reverse(path);
			TreePath tp = new TreePath(path.toArray());
			return tp;
		}
		return null;
	}

	public TreePath vg_getPath(GraphTreeElement node) {
		if(node == null) return null;
		List<TreeNode>path = new ArrayList<TreeNode>();
		if(get_path(node, path)) {
			Collections.reverse(path);
			TreePath tp = new TreePath(path.toArray());
			return tp;
		}
		return null;
	}

	
	public int vg_getType() {
		return type;
	}
	
	public int vg_getGraphModelId_tmp() {
		return graph_model_id;
	}
	
	public int vg_getGraphId_tmp() {
		return graph_id;
	}
	
	public int vg_getVertexId() {
		return vertex_id;
	}
	
	public int vg_getEdgeId() {
		return edge_id;
	}
	
	public int vg_getTabId() {
		return tab_id;
	}
	
//=============================================================================
//--------------MODIFYING METHODS----------------------------------------------
	/**
	 * This method removes all nodes(only leaves) without marked nodes.
	 * @param array - marked nodes.
	 */
	public void vg_removeLeaves4Vertex(Set<Integer>array) {
		for(int i = 0 ; i < this.getChildCount() ; i++) {
			GraphTreeElement gn = (GraphTreeElement)this.getChildAt(i);
			if(gn.isLeaf() && (array == null || !array.contains(gn.vertex_id))) {
				this.remove(i);
				i--;
			} else if(!gn.isLeaf()) {
				gn.vg_removeLeaves4Vertex(array);
				if(gn.isLeaf() && !array.contains(gn.vertex_id)) {
					this.remove(i);
					i--;
				}
			}
		}
	}
	
	public void vg_remove(int node_id, int node_type) {
		if(node_type == DEF_NO_TYPE) return;
		Queue<GraphTreeElement> queue = new ArrayDeque<GraphTreeElement>();
		List<Integer> delete_list = new ArrayList<Integer>();
		queue.add(this);
		while(!queue.isEmpty()) {
			GraphTreeElement curr = queue.poll();
			
			for(int i = 0 ; i < curr.getChildCount() ; i++) {
				GraphTreeElement child = (GraphTreeElement)curr.getChildAt(i);
				if((child.type & node_type & DEF_GRAPH_MODEL_TYPE) > 0 && child.graph_model_id == node_id) {delete_list.add(i);}
				if((child.type & node_type & DEF_GRAPH_TYPE) > 0 && child.graph_id == node_id)       {delete_list.add(i);}
				if((child.type & node_type & DEF_VERTEX_TYPE) > 0 && child.vertex_id == node_id)     {delete_list.add(i);}
				if((child.type & node_type & DEF_EDGE_TYPE) > 0 && child.edge_id == node_id)         {delete_list.add(i);}
				if((child.type & node_type & DEF_TAB_TYPE) > 0 && child.tab_id == node_id)           {delete_list.add(i);}
				if(!child.isLeaf()) queue.add(child);
			}
			for(int i : delete_list) {
				curr.remove(i);
			}
		}
	}
	
	public void vg_remove(int node_type) {
		if(node_type == DEF_NO_TYPE) return;
		Queue<GraphTreeElement> queue = new ArrayDeque<GraphTreeElement>();
		List<Integer> delete_list = new ArrayList<Integer>();
		queue.add(this);
		while(!queue.isEmpty()) {
			GraphTreeElement curr = queue.poll();
			
			for(int i = 0 ; i < curr.getChildCount() ; i++) {
				GraphTreeElement child = (GraphTreeElement)curr.getChildAt(i);
				if((child.type & node_type & DEF_GRAPH_MODEL_TYPE) > 0)    {delete_list.add(i);}
				if((child.type & node_type & DEF_GRAPH_TYPE) > 0)    {delete_list.add(i);}
				if((child.type & node_type & DEF_VERTEX_TYPE) > 0)   {delete_list.add(i);}
				if((child.type & node_type & DEF_EDGE_TYPE) > 0)     {delete_list.add(i);}
				if((child.type & node_type & DEF_TAB_TYPE) > 0)      {delete_list.add(i);}
				if(!child.isLeaf()) queue.add(child);
			}
			for(int i : delete_list) {
				curr.remove(i);
			}
		}
	}
	
	public void vg_add(GraphTreeElement e) {
		add(e);
	}

	public void vg_insert(GraphTreeElement e, int id, int node_type) {
		if(node_type == DEF_NO_TYPE) return;
		Queue<GraphTreeElement> queue = new ArrayDeque<GraphTreeElement>();
		queue.add(this);
		while(!queue.isEmpty()) {
			GraphTreeElement curr = queue.poll();

			for(int i = 0 ; i < curr.getChildCount() ; i++) {
				GraphTreeElement child = (GraphTreeElement)curr.getChildAt(i);
				queue.add(child);
			}

			boolean check = false;
			if((curr.type & node_type & DEF_GRAPH_MODEL_TYPE) > 0 && curr.graph_model_id == id) check = true;
			if((curr.type & node_type & DEF_GRAPH_TYPE) > 0 && curr.graph_id == id) check = true;
			if((curr.type & node_type & DEF_VERTEX_TYPE) > 0 && curr.vertex_id == id) check = true;
			if((curr.type & node_type & DEF_EDGE_TYPE) > 0 && curr.edge_id == id) check = true;
			if((curr.type & node_type & DEF_TAB_TYPE) > 0 && curr.tab_id == id) check = true;
			if(check) {
				curr.add(e); 
			}
		}
	}
	
	public void vg_replaceName(String newName, int id, int node_type) {
		if(node_type == DEF_NO_TYPE) return;
		Queue<GraphTreeElement> queue = new ArrayDeque<GraphTreeElement>();
		queue.add(this);
		while(!queue.isEmpty()) {
			GraphTreeElement curr = queue.poll();

			for(int i = 0 ; i < curr.getChildCount() ; i++) {
				GraphTreeElement child = (GraphTreeElement)curr.getChildAt(i);
				queue.add(child);
			}

			boolean check = false;
			if((curr.type & node_type & DEF_GRAPH_MODEL_TYPE) > 0 && curr.graph_model_id == id) check = true;
			if((curr.type & node_type & DEF_GRAPH_TYPE) > 0 && curr.graph_id == id) check = true;
			if((curr.type & node_type & DEF_VERTEX_TYPE) > 0 && curr.vertex_id == id) check = true;
			if((curr.type & node_type & DEF_EDGE_TYPE) > 0 && curr.edge_id == id) check = true;
			if((curr.type & node_type & DEF_TAB_TYPE) > 0 && curr.tab_id == id) check = true;
			if(check) {
				curr.name = newName; 
			}
		}
	}
	
	public void vg_sort() {
		local_sort(this);
	}
//=============================================================================
//------------------IMPLEMENTATION of STANDARD METHODS-------------------------
	public GraphTreeElement clone() {
		return new GraphTreeElement(this);
	}
	/**
	 * This method converts data of this object to string, which
	 * will to show for user.
	 */
	public String toString() {
		String s = name;
		if(s == null || s.equals(""))
			s = "null";
		return s;
	}
	public List<String> toStringList() {
		List<String>result = new ArrayList<String>(this.getChildCount() * 2);
		result.add(this.toString());
		local_toStringList(this, result);
		return result;
	}
	public List<GraphTreeElement> toList() {
		List<GraphTreeElement>result = new ArrayList<GraphTreeElement>(this.getChildCount() * 2);
		result.add(this);
		local_toList(this, result);
		return result;
	}
	
	@Override
	public int compareTo(GraphTreeElement o) {
		if (o == null) return -1;
		String s1 = toString();
		String s2 = o.toString();
		return s1.compareTo(s2);
	}
//=============================================================================
//------------------------PRIVATE METHODS--------------------------------------
	private boolean get_path(int node_id, int node_type, List<TreeNode>path) {
		if((node_type & DEF_GRAPH_MODEL_TYPE) > 0 && graph_model_id == node_id) {path.add(this); return true;}
		if((node_type & DEF_GRAPH_TYPE) > 0 && graph_id == node_id) {path.add(this); return true;}
		if((node_type & DEF_VERTEX_TYPE) > 0 && vertex_id == node_id) {path.add(this); return true;}
		if((node_type & DEF_EDGE_TYPE) > 0 && edge_id == node_id) {path.add(this); return true;}
		if((node_type & DEF_TAB_TYPE) > 0 && tab_id == node_id) {path.add(this); return true;}

		for(int i = 0 ; i < getChildCount() ; i++) {
			GraphTreeElement gn = (GraphTreeElement)getChildAt(i);
			if(gn.get_path(node_id, node_type, path)) {
				path.add(this);
				return true;
			}
		}
		return false;
	}
	
	private boolean get_path(GraphTreeElement node, List<TreeNode>path) {
		if (node == this) {path.add(this); return true;}

		for(int i = 0 ; i < getChildCount() ; i++) {
			GraphTreeElement gn = (GraphTreeElement)getChildAt(i);
			if(gn.get_path(node, path)) {
				path.add(this);
				return true;
			}
		}
		return false;
	}
	
	private void local_toStringList(GraphTreeElement currNode, List<String> currList) {
		if (currNode == null) return;
		for(int i = 0; i < currNode.getChildCount(); i++) {
			GraphTreeElement gn = (GraphTreeElement)currNode.getChildAt(i);
			currList.add(gn.toString());
			local_toStringList(gn, currList);
		}
	}
	
	private void local_toList(GraphTreeElement currNode, List<GraphTreeElement> currList) {
		if (currNode == null) return;
		for(int i = 0; i < currNode.getChildCount(); i++) {
			GraphTreeElement gn = (GraphTreeElement)currNode.getChildAt(i);
			currList.add(gn);
			local_toList(gn, currList);
		}
	}
	
	private void local_sort(GraphTreeElement currNode) {
		if (currNode == null || currNode.getChildCount() == 0) return;
				
		// sort nodes
		GraphTreeElement[] tmp_nodes = new GraphTreeElement[currNode.getChildCount()];
		currNode.children.toArray(tmp_nodes);
		Arrays.sort(tmp_nodes);
		
		// remove unsorted nodes and add sorted nodes
		currNode.removeAllChildren();
		for (int i = 0; i < tmp_nodes.length; i++) {
			currNode.add(tmp_nodes[i]);
			local_sort(tmp_nodes[i]);
		}
	}
}
